import os
import re
import sys
import string
from md5 import md5
from warnings import warn
import logging

from google.appengine.ext import db
from google.appengine.api import users
from google.appengine.api import mail
from google.appengine.ext.webapp import template

from glog import AppEnv
from glog.base import Error
from glog.models import AppConfig
from glog.models import Theme
from glog import utils

class Blog(db.Model):
    owner = db.UserProperty(required=True)
    blogId = db.StringProperty(required=True, default='Sevenever')
    blogName = db.StringProperty(required=True, default='Sevenever\'s blog')
    blogDesc = db.TextProperty(default='')
    
    postsPerPage = db.IntegerProperty(default=20)
    rssCount = db.IntegerProperty(default=20)
    postsPerPageList = db.IntegerProperty(default=50)

    apiPass = db.StringProperty(default='')

    theme = db.ReferenceProperty(Theme.Theme)
    widgets = db.StringListProperty()
    
    # fileds for statistic
    postCount = db.IntegerProperty(default=0)
    tagCount = db.IntegerProperty(default=0)
    commentCount = db.IntegerProperty(default=0)

    def updateComment(self, count):
        self.commentCount += count
        if self.commentCount < 0:
            logging.warn("after %s, %s < 0, set to 0." % ('updateComment', 'commentCount'))
            self.commentCount = 0
        self.put()
    
    def updatePost(self, count):
        self.postCount += count
        if self.postCount < 0:
            logging.warn("after %s, %s < 0, set to 0." % ('updatePost', 'postCount'))
            self.postCount = 0
        self.put()
    
    def updateTag(self, count):
        self.tagCount += count
        if self.tagCount < 0:
            logging.warn("after %s, %s < 0, set to 0." % ('updateTag', 'tagCount'))
            self.tagCount = 0
        self.put()
    
    def reset(self):
        self.postsPerPage = 20
        self.rssCount = 20
        self.postsPerPageList = 50

        self.theme = Theme.getDefaultTheme()
        self.put()

    # FixMe use more grace pythonic way, do not use method
    def setAPIPassword(self, s):
        self.apiPass =  utils.getPasswordHash(self.owner.email(),s)
        
    def checkAPIPassword(self, username, password):
        return self.apiPass == utils.getPasswordHash(username,password)

    def delete(self):
        # delete all post
        posts = self.post_set
        for post in posts:
            post.delete()
            
        db.Model.delete(self)

def is_blogger(email):
    """Is the user with email is a blogger?
    Args:
        email: a email address.
    Returns:
        If is blogger, return the Blog object
        else return False.
    """
    user = users.User(email)
    blogs = Blog.all().filter('owner = ', user).fetch(limit=1)
    if blogs:
        return blogs[0]
    else:
        return False

                    
def get_blog(blogid):
    blogs = Blog.all().filter('blogId = ', blogid).fetch(limit=1)
    if blogs:
        return blogs[0]
    return None

def is_current_user_blogowner(blogid):
    blogs = Blog.all().filter('blogId = ', blogid).filter('owner = ',users.get_current_user()).fetch(limit=1)
    if blogs:
        return True
    else:
        return False

def new_blog(owner, blogId, blogName, postsPerPage=20, rssCount=20, postsPerPageList=50):
    """create a new blog.
    Args:
        owner: the owner's address, must be a valid gmail address.
        blogId: the blogId, must matched regex r'^\w{6,30}$'.
        blogName: the blog name displayed.
    Returns:
        the new blog object.
    """
    author = users.User(owner)
    blog = Blog(
                owner = author,
                blogId = blogId,
                blogName = blogName,
                postsPerPage = postsPerPage,
                rssCount = rssCount,
                postsPerPageList = postsPerPageList,
                theme = Theme.getDefaultTheme(),
                )
    blog.setAPIPassword('yamede')
    blog.put()

    # send a notification mail to userbase/
    path = os.path.join(AppEnv.app_root, 'template', 'default', Theme.NewUser_mail)
    body = template.render(path, {'nickname':author.nickname(),
                                  'blogName':blogName,
                                  'HostURL':AppConfig.getAppConfig().HostURL,
                                  'apppath': AppEnv.apppath,
                                  'blogId': blogId,
                                  'adminemail': AppEnv.adminemail,
                                  })

    mail.send_mail(sender= AppEnv.adminemail,
                   to = owner,
                   subject = 'Your blog [%s] has created' % blogId,
                   body = body)
    return blog

def delete_blog(blogId):
    blogs = Blog.all().filter('blogId = ', blogId).fetch(limit=1)
    if blogs:
        blog = blogs[0]
        blog.delete()
        return
    else:
        raise Error.NoThisObjectError()

def reset_blog(blogId):
    blogs = Blog.all().filter('blogId = ', blogId).fetch(limit=1)
    if blogs:
        blog = blogs[0]
        blog.reset()
        return
    else:
        raise Error.NoThisObjectError()

# for widgets
"""
Widget ID must be English chars.
Widget can have parameters, parameters will pass to widget function as string.
example:
RecentComment/5
will call widget function RecentComment(handler, '5').

Actually, the Widget string will append to the URL to fetch widget page.

Widgets is stored in format of list of str.
use str startswith # as group spilter.

example:
widgets = [ '######',
            'BlogInfo',
            'Tags',
            '######',
            'Posts/5',
            '######',
            'Archives',
            ]
has three groups:
    group 1:    BlogInfo, Tags
    group 2:    Posts
    group 3:    Archives
"""
def attachWidgets(dic, widgets):
    """attach widgets to rh(RequestHandler's templatedic
    """
    # clear all widgets
    i = 1
    while True:
        if not dic.has_key('wgroup%d'%i):
            break
        del dic['wgroup%d'%i]
        i += 1
    # reattatch
    i = 1
    l = []
    for w in widgets:
        w = str(w)
        if w.startswith('#'):
            if l:
                dic['wgroup%d'%i] = l
                i += 1
                l = []
        else:
            l.append(w)
    if l:
        dic['wgroup%d'%i] = l

    return

def parseWidgets(s):
    s = s.strip()
    wg = []    # widget group
    gl = []    # group list
    # split into groups
    for k in s.split('\n'):
        k = k.strip()
        if k:
            if k.startswith('#') or k.startswith('-') or k.startswith('*'):
                if wg:
                    gl.append(wg)
                wg = []
            else:
                if not re.match(r'^\w[\w/]+$',k):
                    raise Error.InvalidWidgetIdError('Invalid widget id %s' % k)
                wg.append(k)
    if wg:
        gl.append(wg)

    # composite groups
    wl = []    # widgets
    for i, wg in enumerate(gl):
        wl.append('###Group(%d)###' % (i+1))
        for w in wg:
            wl.append(w)
    return wl
    
def checkowner(s):
    """Check s is a valid gmail address
    """
    if not re.match('^([a-zA-Z0-9_\-\.]+)@gmail.com$', s, re.I):
        return False, 'Owner is not a valid Gmail address.'
    return True, None

__reservedBlogIds__ = [
    'ajax',
    'api',
    'themes',
    'admin',
    'js',
    'css',
    'img',
    'openid',
    'tag',
    'theme',
    'post',
    'widget',
    'rss',
    'seven',
    ]
__blogId_re__ = re.compile(r'^\w{6,30}$')
def checkblogId(s):
    # must be regex r'\w'
    if not s:
        return False,'Blog ID is empty.'
    if s in __reservedBlogIds__:
        return False, 'Blog ID "%s" is reverved.' % s
    if not __blogId_re__.match(s):
        return False, 'Blog ID must be 6-30 bit number or English charactors.'
    # is exists?
    blog = Blog.all().filter(' blogId = ', s).fetch(limit=1)
    if blog:
        return False, 'Blog ID not available.'
    return True, None

def checkblogName(s):
    if not s:
        return False,'Blog Name is empty.'
    if len(s)>100:
        return False,'Blog Name is too long(0-100).'
    return True,None
    

def checkpostsPerPage(s):
    if not s:
        return False,'Post Per Page is empty.'
    try:
        s = int(s)
    except:
            return False,'Post Per Page must be an integer(1-99).'
    if s<1:
        return False,'Post Per Page is too small(1-99).'
    if s>99:
        return False,'Post Per Page is too big(1-99).'
    return True,None

def checkrssCount(s):
    if not s:
        return False,'Rss Output Count is empty.'
    try:
        s = int(s)
    except:
        return False,'Rss Output Count must be an integer(1-99).'
    if s<1:
        return False,'Rss Output Count is too small(1-99).'
    if s>99:
        return False,'Rss Output Count is too big(1-99).'
    return True,None

def checkpostsPerPageList(s):
    if not s:
        return False,'Post Per Page in Summary is empty.'
    try:
            s = int(s)
    except:
        return False,'Post Per Page in Summary must be an integer(1-99).'
    if s<1:
        return False,'Post Per Page in Summary is too small(1-99).'
    if s>99:
        return False,'Post Per Page in Summary is too big(1-99).'
    return True,None

__valid_password_char__ =  string.ascii_letters + string.digits + string.punctuation
def checkapiPass(s):
    if not s:
        return False,'API Password is empty.'
    for c in s:
        if not c in __valid_password_char__:
            return False,'API Password must be English letters or digits or punctuations.'
    if len(s) < 6 or len(s)>20:
        return False,'API Password length must between 6~20.'
    return True, None

def checkAll(owner, blogId, blogName, postsPerPage=20, rssCount=20, postsPerPageList=50, apiPass=None):
    cl = [
        checkowner(owner),
        checkblogId(blogId),
        checkblogName(blogName),
        checkpostsPerPage(postsPerPage),
        checkrssCount(rssCount),
        checkpostsPerPageList(postsPerPageList),
          ]
    if apiPass:
        cl.append(checkapiPass(apiPass))
    else:
        cl.append((True,None))
    return all([cr[0] for cr in cl]),[cr[1] for cr in cl if not cr[0]]
